#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;

namespace Yarr
{
    /// <summary>
    /// A debugger stack frame
    /// </summary>
    public sealed class StackFrame
    {
        internal Object form;
        private Environment env;
        private int depth;

        /// <summary>
        /// The form being evaluated in this stack frame
        /// </summary>
        public Object Form { get { return form; } }

        /// <summary>
        /// The execution environment for this stack frame
        /// </summary>
        public Environment ExecutionEnvironment { get { return env; } }

        /// <summary>
        /// The stack depth of this stack frame
        /// </summary>
        public int StackDepth { get { return depth; } }

        private StackFrame() { }

        private StackFrame(Object form, Environment env, int depth)
        {
            this.form = form;
            this.env = env;
            this.depth = depth;
        }

        internal static StackFrame MakeStackFrame (Object form, Environment env, int depth)
        {
            return new StackFrame(form, env, depth);
        }
    }


    internal class DebugProfiler : IProfiler, IDebugger 
    {
        private Dictionary<Thread, Stack<StackFrame>> callStack;
        private Dictionary<object, bool> breakPoints;

        private bool isActive;      // if true, debugger is currently active
        private bool isStepping;    // if true, we are single-stepping
        private bool isCompleting;  // if true, we are running to completion
        private int  stepLevel;     // lowest stack-frame level at which to single-step

        private IDebuggerBreak debugBreak;  // Callbacks for debug breaks

        private static DebugProfiler theInstance;

        /// <summary>
        /// Returns the debugger instance
        /// </summary>
        /// <returns></returns>
        public static IDebugger MakeDebugger(IDebuggerBreak breakInstance)
        {
            if (theInstance == null)
                theInstance = new DebugProfiler();
            else
                theInstance.Reset();

            theInstance.debugBreak = breakInstance;
            return theInstance;
        }

        private DebugProfiler () 
        {
            Reset();
        }

        private void Reset ()
        {
            this.isActive = true;
            this.isCompleting = false;
            this.isStepping = true;
            this.stepLevel = -1;
        }

#region IProfiler implmentation

        /// <summary>
        /// Initialize debugger
        /// </summary>
        public void Open ()
        {
            if (callStack == null)
                callStack = new Dictionary<Thread, Stack<StackFrame>>();

            if (breakPoints == null)
                breakPoints = new Dictionary<object, bool>();
        }

        /// <summary>
        /// If debugging, add the current call to the call stack
        /// </summary>
        /// <param name="form"></param>
        /// <param name="env"></param>
        public object TraceCall (object form, Environment env)
        {
            // Track the stack frame for this thead
            if (isActive && !isCompleting)
            {
                Stack<StackFrame> stack;
                StackFrame frame;
                Thread curr = Thread.CurrentThread;

                bool stackExists;

                lock (callStack)
                {
                    stackExists = callStack.ContainsKey(curr);
                }

                if (!stackExists)
                {
                    lock (callStack)
                    {
                        callStack[curr] = stack = new Stack<StackFrame>();
                    }
                }
                else
                {
                    lock (callStack)
                    {
                        stack = callStack[curr];
                    }
                }

                stack.Push(frame = StackFrame.MakeStackFrame(form, env, stack.Count));

                if ((isStepping && (stepLevel == -1 || stack.Count <= stepLevel)) || IsBreakpoint(frame))
                {
                    isActive = false;
                    form =  debugBreak.DebugBreak (this, frame);
                    frame.form = form;
                    isActive = true;
                }
            }

            return form;
        }

        /// <summary>
        /// Defualt implementation -- does nothing
        /// </summary>
        /// <param name="comment"></param>
        /// <param name="env"></param>
        public void Comment (string comment, Environment env)
        {
            if (isActive && !isCompleting)
            {
                isActive = false;
                debugBreak.DebugPrint(this, comment, env);
                isActive = true;
            }
        }

        /// <summary>
        /// Defualt implementation -- does nothing
        /// </summary>
        /// <param name="form"></param>
        /// <param name="env"></param>
        /// <returns></returns>
        public object TraceReturn (object form, Environment env)
        {
            // Always catch exceptions, even if isActive is false
            if ((isActive && !isCompleting) || (form is System.Exception))
            {
                Thread curr = Thread.CurrentThread;

                bool stackExists;
                lock (callStack)
                {
                    stackExists = callStack.ContainsKey(curr);
                }

                if (stackExists)
                {
                    StackFrame frame;

                    lock (callStack)
                    {
                        frame = callStack[curr].Pop();
                    }

                    if ((form is System.Exception) || 
                        ((isStepping && (stepLevel == -1 || 
                                         frame.StackDepth <= stepLevel)) || 
                                         IsBreakpoint(frame)))
                    {
                        isActive = false;
                        form = debugBreak.ReturnValue(this, frame, form);
                        isActive = true;
                    }
                }
            }

            return form;
        }

        /// <summary>
        /// Defualt implementation -- does nothing
        /// </summary>
        public void Close ()
        {
            callStack = null;
            isActive = false;
        }
#endregion

#region IDebugger implementation

        /// <summary>
        /// Add a breakpoint
        /// </summary>
        /// <param name="fn">The function or form on which to set the breakpoint</param>
        public void AddBreakpoint (Object fn)
        {
            breakPoints[fn] = true;
        }

        /// <summary>
        /// Remove a breakpoint
        /// </summary>
        /// <param name="fn">The function or form from which to remove the breakpoint</param>
        public void RemoveBreakpoint (Object fn)
        {
            if (breakPoints.ContainsKey(fn))
                breakPoints.Remove(fn);
        }

        /// <summary>
        /// Enable an existing breakpoint
        /// </summary>
        /// <param name="fn">The function or form on which to enable the existing breakpoint</param>
        public void EnableBreakpoint (Object fn)
        {
            if (breakPoints.ContainsKey(fn))
                breakPoints[fn] = true;
        }

        /// <summary>
        /// Disable an existing breakpoint
        /// </summary>
        /// <param name="fn">The function or form on which to disable the existing breakpoint</param>
        public void DisableBreakpoint (Object fn)
        {
            if (breakPoints.ContainsKey(fn))
                breakPoints[fn] = false;
        }

        /// <summary>
        /// Get the current thread's call stack
        /// </summary>
        /// <returns>The call stack</returns>
        public StackFrame[] GetCallstack ()
        {
            if (callStack == null)
                return null;

            Thread curr = Thread.CurrentThread;
            return callStack.ContainsKey(curr) ? callStack[curr].ToArray() : null;
        }

        public Object[] GetBreakpoints ()
        {
            Object[] bkpts = new Object[breakPoints.Keys.Count];
            breakPoints.Keys.CopyTo(bkpts, 0);
            return bkpts;
        }

        public bool IsBreakpointEnabled (Object bkpt)
        {
            return breakPoints.ContainsKey(bkpt) ? breakPoints[bkpt] : false;
        }

        /// <summary>
        /// Set the debugger mode
        /// </summary>
        /// <param name="mode">The mode to set</param>
        /// <param name="frame">The mode will be set in the context of this stack frame</param>
        /// <param name="argument">An argument for the mode, if applicable</param>
        public void SetMode (DebuggerMode mode, StackFrame frame, int argument)
        {
            switch (mode)
            {
                case DebuggerMode.StepOver:
                    isStepping = true;
                    stepLevel = frame.StackDepth;
                    break;

                case DebuggerMode.SingleStep:
                    isStepping = true;
                    stepLevel = -1;
                    break;

                case DebuggerMode.RunToBreakpoint:
                    isStepping = false;
                    break;

                case DebuggerMode.RunToPreviousFrame:
                    isStepping = true;
                    stepLevel = frame.StackDepth - argument;
                    break;

                case DebuggerMode.RunToCompletion:
                    isCompleting = true;
                    if (callStack.ContainsKey(Thread.CurrentThread))
                        callStack.Remove(Thread.CurrentThread);
                    break;

                case DebuggerMode.Abort:
                    isCompleting = true;
                    if (callStack.ContainsKey(Thread.CurrentThread))
                        callStack.Remove(Thread.CurrentThread);
                    throw new YarrException("Eval: Execution aborted by debugger");
            }
        }
#endregion

        private bool IsBreakpoint (StackFrame frame)
        {
            Object form = frame.Form;
            if (form is Cons)
            {
                form = ((Cons)frame.Form).Car();

                if (form is Symbol)
                {
                    if (!breakPoints.ContainsKey(form))
                    {

                        if ((form == Symbol.CALL  ||  form == Symbol.CALLINST)
                                    && ((Cons)frame.Form).Second() != null)
                            form = ((Cons)frame.Form).Second();

                        else if (frame.ExecutionEnvironment.Contains((Symbol)form))
                            form = frame.ExecutionEnvironment.GetValue((Symbol)form);
                    }
                }
            }

            return breakPoints.ContainsKey(form) ? breakPoints[form] : false;
        }
    }
}
